#include <IDK.hpp>

#include <Resources.hpp>
#include <Texture.hpp>

namespace Core
{

	static const uint PixelTableSize[IDK::Pixel::Type::MAX] =
	{
		2, // RGBA4
		8, // RGBA8
		8, // RGB_DXT1
		8, // RGBA_DXT1
		16, // RGBA_DXT3
		16, // RGBA_DXT5
		2, // RGB_PVR2
		4 // RGBA_PVR4
	};

	template<int TCodec>
	struct TTextureCodecReader
	{
		static void ReadHeader(ref<IDK::IStream> reader);
		static uint GetSizeInBytes(uint width, uint height, IDK::Pixel::Type::Enum pixelType);
	};

	typedef TTextureCodecReader<TextureCodec::Value> TextureCodecReader;

	//
	// DDS
	//
	template<>
	inline void TTextureCodecReader<TextureCodec::DDS>::ReadHeader(ref<IDK::IStream> reader)
	{
		struct DDSHeader
		{
			char magic[4];
			uint size, flags, height, width, memSize, depth, mipcnt, reserved1[11];
			uint pxSize, pxFlags, pxFour, pxInfo[5];
			uint caps[4];
			uint reserved2;
		} header;

		reader->ReadData<DDSHeader>(header);
	}


	template<>
	inline uint TTextureCodecReader<TextureCodec::DDS>::GetSizeInBytes(uint width, uint height, IDK::Pixel::Type::Enum pixelType)
	{
		switch(pixelType)
		{
		case IDK::Pixel::Type::RGB_DXT1:
		case IDK::Pixel::Type::RGBA_DXT1:
		case IDK::Pixel::Type::RGBA_DXT3:
		case IDK::Pixel::Type::RGBA_DXT5:
			return ((width + 3) / 4) * ((height + 3) / 4) * PixelTableSize[pixelType];
		default: 
			iError("Not support pixel type");
		}
	}


	//
	// PVRTC
	//
	template<>
	inline void TTextureCodecReader<TextureCodec::PVRTC>::ReadHeader(ref<IDK::IStream> reader)
	{
		struct PVRTCHeader
		{
			uint headerLength;
			uint height;
			uint width;
			uint numMipmaps;
			uint flags;
			uint dataLength;
			uint bpp;
			uint bitmaskRed;
			uint bitmaskGreen;
			uint bitmaskBlue;
			uint bitmaskAlpha;
			uint pvrTag;
			uint numSurfs;
		} header;

		reader->ReadData<PVRTCHeader>(header);
	}


	template<>
	inline uint TTextureCodecReader<TextureCodec::PVRTC>::GetSizeInBytes(uint width, uint height, IDK::Pixel::Type::Enum pixelType)
	{
		switch(pixelType)
		{
		case IDK::Pixel::Type::RGBA4:
			return width * height * PixelTableSize[pixelType];
		case IDK::Pixel::Type::RGBA_PVR2:
		case IDK::Pixel::Type::RGBA_PVR4:
			return ((width) * (height) * PixelTableSize[pixelType] + 7) / 8;
		default: 
			iError("Not support pixel type");
		}
	}

	//
	// TTextureMip
	//

	//-------------------------------------------------------
	// TTextureMip
	//-------------------------------------------------------
	TTextureMip::TTextureMip(Threading::IThreadEvent& signal, ref<IDK::IStream> stream, IDK::Pixel::Type::Enum pixelType, uint priority, uint width, uint height)
		: Base(signal, stream, priority),
		mPixelType(pixelType),
		mWidth(width),
		mHeight(height),
		mLoadProcess(LoadProcess::None)
	{
	}

	//-------------------------------------------------------
	// SetLevel
	//-------------------------------------------------------
	void TTextureMip::SetLevel(uint level)
	{
		mLevel = level;
	}

	//-------------------------------------------------------
	// GetLevel
	//-------------------------------------------------------
	uint TTextureMip::GetLevel(void)
	{
		return mLevel;
	}

	//-------------------------------------------------------
	// GetWidth
	//-------------------------------------------------------
	uint TTextureMip::GetWidth(void)
	{
		return mWidth;
	}

	//-------------------------------------------------------
	// GetHeight
	//-------------------------------------------------------
	uint TTextureMip::GetHeight(void)
	{
		return mHeight;
	}

	//-------------------------------------------------------
	// GetData
	//-------------------------------------------------------
	TTextureMip::PixelData TTextureMip::GetData(void)
	{
		return mPixels;
	}

	//-------------------------------------------------------
	// GetSizeInBytes
	//-------------------------------------------------------
	uint TTextureMip::GetSizeInBytes(void)
	{
		return TextureCodecReader::GetSizeInBytes(mWidth, mHeight, mPixelType);
	}

	//-------------------------------------------------------
	// Cleanup
	//-------------------------------------------------------
	void TTextureMip::Cleanup(void)
	{
		//Reset streaming helpers flags
		mLoadProcess = LoadProcess::None;

		if(mPixels != NullPtr)
		{
			iAssert(mPixels->IsUnique(), "Pixel data is used other smart ptr's");
			mPixels = NullPtr;
		}

		Base::Cleanup();
	}

	//-------------------------------------------------------
	// OnTickLoad
	//-------------------------------------------------------
	bool TTextureMip::OnTickLoad(void)
	{

		ref<IDK::IStream> reader = GetResourceStream();
		//
		switch(mLoadProcess)
		{
		case LoadProcess::None:
			reader->Open();
			//iLog("Texture to load:");
			//iLog(reader->GetPath()->Chars());
			mLoadProcess = LoadProcess::ReadHeader; //next
			break;

		case LoadProcess::ReadHeader:
			TextureCodecReader::ReadHeader(reader);

			mLoadProcess = LoadProcess::InitPixelData; //next
			break;

		case LoadProcess::InitPixelData:
			mPixels = PixelData(new PixelData::Create(GetSizeInBytes()));
			mPixelDataReadInBytes = 0;
			mLoadProcess = LoadProcess::ReadPixelData; //next
			break;

		case LoadProcess::ReadPixelData:
			{
				const uint MaxSizeInBytes = GetSizeInBytes();
				const uint NeedReadInBytes = 65535; //max read bytes for one iteration
				const uint PosRead = MaxSizeInBytes - mPixelDataReadInBytes;

				if(PosRead > NeedReadInBytes)
				{
					reader->ReadData(mPixels, NeedReadInBytes, mPixelDataReadInBytes);
					mPixelDataReadInBytes += NeedReadInBytes;
				}
				else
				{
					reader->ReadData(mPixels, PosRead, mPixelDataReadInBytes);
					mPixelDataReadInBytes = 0;
					mLoadProcess = LoadProcess::Complete; //next
				}
			}
			break;

		case LoadProcess::Complete:
			reader->Close();
			mLoadProcess = LoadProcess::None;
			return true;
		}
		return false;
	}

	//
	// TTexture
	//

	//-------------------------------------------------------
	// TTexture
	//-------------------------------------------------------
	TTexture::TTexture(const String &path, IDK::Pixel::Type::Enum pixelType): Base(),
		mPath(path),
		mPixelType(pixelType)
	{
	}

	//-------------------------------------------------------
	// GetPath
	//-------------------------------------------------------
	String TTexture::GetPath(void)
	{
		return mPath;
	}

	//-------------------------------------------------------
	// GetWidth
	//-------------------------------------------------------
	uint TTexture::GetWidth(void)
	{
		iAssert(Length() != 0, "Cant get width texture, not assign mip's");
		return Get(0)->GetWidth();
	}

	//-------------------------------------------------------
	// GetHeight
	//-------------------------------------------------------
	uint TTexture::GetHeight(void)
	{
		iAssert(Length() != 0, "Cant get height texture, not assign mip's");
		return Get(0)->GetHeight();
	}

	//-------------------------------------------------------
	// SetPixelType
	//-------------------------------------------------------
	void TTexture::SetPixelType(IDK::Pixel::Type::Enum fmt)
	{
		mPixelType = fmt;
	}

	//-------------------------------------------------------
	// GetPixelType
	//-------------------------------------------------------
	IDK::Pixel::Type::Enum TTexture::GetPixelType(void)
	{
		return mPixelType;
	}

	//-------------------------------------------------------
	// AddMip
	//-------------------------------------------------------
	void TTexture::AddMip(TextureMip mip)
	{
		mMipBind = Length();
		mip->SetLevel(mMipBind);
		Add(mip);
	}

	//-------------------------------------------------------
	// GetMipCount
	//-------------------------------------------------------
	uint TTexture::GetMipCount(void)
	{
		return Length();
	}

	//-------------------------------------------------------
	// GetMinMagFilter
	//-------------------------------------------------------
	IDK::Pixel::Filter::Enum TTexture::GetFilter(void) const
	{
		//TODO: move to constructor
		return (Length() > 1) ? IDK::Pixel::Filter::MipMap : IDK::Pixel::Filter::Point;
	}

	void TTexture::BindGpuResource(IDK::IRenderState& rndState)
	{
		if(mTexture == NullPtr || mTexture->IsInvalid())
		{
			mTexture = rndState.CreateTexture(GetPixelType(), GetFilter());
		}
	}

	//-------------------------------------------------------
	// BindTexture
	//-------------------------------------------------------
	void TTexture::BindTexture(IDK::IRenderState& rndState, uint index)
	{
		BindGpuResource(rndState);
		rndState.SetTexture(mTexture, index);

		TextureMip mip = Get(mMipBind);

		switch(mip->GetLoadStatus())
		{				
		case IDK::IResource::LoadStatus::Wait:
			mip->SetLoadStatus(IDK::IResource::LoadStatus::Loading);
			break;

		case IDK::IResource::LoadStatus::Loaded:
			//bind resource
			mTexture->Update(
				mMipBind,
				mip->GetWidth(),
				mip->GetHeight(),
				mip->GetData().As<HandlePtr::Instance>(),
				mip->GetSizeInBytes());

			mip->SetLoadStatus(IDK::IResource::LoadStatus::Ready);
			mMipBind = (mMipBind == TextureConfig::BaseMipLevel) ? mMipBind : --mMipBind;
			mip->Cleanup();
			break;
		}
	}
}
